/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.loaders;
import java.awt.BorderLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Set;
import java.util.HashSet;
import java.util.AbstractCollection;
import java.util.ResourceBundle;
import java.util.Collections;
import java.util.Iterator;
import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.EventListenerList;
import org.openide.*;
import org.openide.util.datatransfer.*;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.util.*;
import org.openide.util.enum.*;
import org.openide.util.actions.SystemAction;
import org.openide.util.actions.Presenter;
import org.openide.nodes.*;
/** Object that represents one JavaBean in the tree of beans representing
* data systems.
*
* @author Jaroslav Tulach, Petr Hamernik, Jan Jancura, Ian Formanek
*/
public abstract class DataObject extends Object implements Node.Cookie, Serializable {
/** generated Serialized Version UID */
static final long serialVersionUID = 3328227388376142699L;
/** Name of the template property. */
public static final String PROP_TEMPLATE = "template"; // NOI18N
/** Name of the name property. */
public static final String PROP_NAME = "name"; // NOI18N
/** Name of the help context property. */
public static final String PROP_HELP = "helpCtx"; // NOI18N
/** Name of the modified property. */
public static final String PROP_MODIFIED = "modified"; // NOI18N
/** Name of the property used during notification of changes in the set of cookies attached to this object. */
public static final String PROP_COOKIE = Node.PROP_COOKIE;
/** Name of valid property. Allows listening to deletion or disposal of the data object. */
public static final String PROP_VALID = "valid"; // NOI18N
/** Name of primary file property. Primary file is changed when the object is moved */
public static final String PROP_PRIMARY_FILE = "primaryFile"; // NOI18N
/** Name of files property. Allows listening to set of files handled by this object. */
public static final String PROP_FILES = "files"; // NOI18N
/** Extended attribute for holding the class of the loader that should
* be used to recognize a file object before the normal processing takes
* place.
*/
static final String EA_ASSIGNED_LOADER = "NetBeansAttrAssignedLoader"; // NOI18N
/** all modified data objects contains DataObjects.
* ! Use syncModified for modifications instead !*/
private static ModifiedRegistry modified = new ModifiedRegistry();
/** sync modified data (for modification operations)
* @associates DataObject*/
private static Set syncModified = Collections.synchronizedSet(modified);
/** Modified flag */
private boolean modif = false;
/** the node delegate for this data object */
private transient Node nodeDelegate;
/** item with info about this data object */
DataObjectPool.Item item;
/** the loader for this data object */
private DataLoader loader;
/** property change listener support */
private EventListenerList listeners;
/** Create new data object.
* @param pf primary file object for this data object
* @param loader loader that created the data object
* @exception DataObjectExistsException if there is already a data object
* for this primary file
*/
public DataObject (FileObject pf, DataLoader loader) throws DataObjectExistsException {
this (pf, DataObjectPool.POOL.register (pf, loader), loader);
}
/** Private constructor. At this time the constructor receives
* the primary file and pool item where it should register itself.
*
* @param pf primary file
* @param item the item to register into
* @param loader loader that created the data object
*/
private DataObject (FileObject pf, DataObjectPool.Item item, DataLoader loader) {
this.item = item;
this.loader = loader;
item.setDataObject (this);
}
/* Finalize. Disposes the handler (calls {@link #dispose}).
*/
protected final void finalize () {
DataObjectPool.Item item = this.item;
if (item != null) {
item.deregister (false);
}
// System.out.println ("DataObject finalized: " + getPrimaryFile ()); // NOI18N
}
// This method first unregisters the object, then calls method unreferenced.
// After that it asks the parent folder to regenerate its list of children,
// so different object is usually created for primary file of this object.
/** Allows subclasses to discard the object. When an object is discarded,
* it is released from the list of objects registered in the system.
* Then the contents of the parent folder (if it still exists) are rescanned, which
* may result in the creation of a new data object for the primary file.
* <P>
* The normal use of this method is to change the type of a data object.
* Because this would usually only be invoked from
* the original data object, it is protected.
*/
protected void dispose () {
DataObjectPool.Item item = this.item;
if (item != null) {
item.deregister (true);
item.setDataObject(null);
firePropertyChange (PROP_VALID, Boolean.FALSE, Boolean.TRUE);
}
}
/** Setter that allows to destroy this data object. Because such
* operation can be dangerous and not always possible (if the data object
* is opened in editor) it can be vetoed. Either by this data object
* or by any vetoable listener attached to this object (like editor support)
*
* @param valid should be false
* @exception PropertyVetoException if the invalidation has been vetoed
*/
public void setValid (boolean valid) throws PropertyVetoException {
if (!valid && isValid ()) {
fireVetoableChange (PROP_VALID, Boolean.FALSE, Boolean.TRUE);
dispose ();
}
}
/** Test whether the data object is still valid and usable.
* <P>
* The object can become invalid when it is deleted, its files are deleted, or
* {@link #dispose} is called.
* <P>
* When the validity of the object changes a property change event is fired, so
* anyone can listen and be notified when the object is deleted/disposed.
*/
public final boolean isValid () {
return item.isValid ();
}
/** Get the loader that created this data object.
* @return the data loader
*/
public final DataLoader getLoader () {
return loader;
}
/** Mark all contained files as belonging to this loader.
* If the files are rescanned (e.g. after a disposal), the current data loader will be given preference.
*/
protected final void markFiles () throws IOException {
Iterator en = files ().iterator ();
while (en.hasNext ()) {
FileObject fo = (FileObject)en.next ();
loader.markFile (fo);
}
}
/** Get all contained files.
* These file objects should ideally have had the {@link FileObject#setImportant important flag} set appropriately.
* <P>
* The default implementation returns a set consisting only of the primary file.
*
* @return set of {@link FileObject}s
*/
public Set files () {
return java.util.Collections.singleton (getPrimaryFile ());
}
/** Get the node delegate. Either {@link #createNodeDelegate creates it} (if it does not
* already exist) or
* returns a previously created instance of it.
*
* @return the node delegate (without parent) for this data object
*/
public final Node getNodeDelegate () {
if (nodeDelegate == null) {
synchronized (this) {
if (nodeDelegate == null) {
nodeDelegate = createNodeDelegate ();
}
}
}
return nodeDelegate;
}
/** This method allows DataFolder to filter its nodes.
*
* @param filter filter for subdata objects
* @return the node delegate (without parent) the node is new instance
* of node and can be inserted to any place in the hierarchy
*/
Node getClonedNodeDelegate (DataFilter filter) {
return getNodeDelegate ().cloneNode ();
}
// When a node for representation
// in a parent is requested by a call to getNode (parent) it is the exact copy of this node
// with only parent changed.
/** Provides node that should represent this data object.
* <p>The default implementation creates an instance of {@link DataNode}.
* <P>
* This method is called only once per data object.
*
* @return the node delegate (without parent) for this data object
*/
protected Node createNodeDelegate () {
return new DataNode (this, Children.LEAF);
}
/** Obtains lock for primary file.
*
* @return the lock
* @exception IOException if taking the lock fails
*/
protected FileLock takePrimaryFileLock () throws IOException {
return getPrimaryFile ().lock ();
}
/** Package private method to assign template attribute to a file.
* Used also from FileEntry.
*
* @param fo the file
* @param newTempl is template or not
* @return true if the value change/false otherwise
*/
static boolean setTemplate (FileObject fo, boolean newTempl) throws IOException {
boolean oldTempl = false;
Object o = fo.getAttribute(DataObject.PROP_TEMPLATE);
if ((o instanceof Boolean) && ((Boolean)o).booleanValue())
oldTempl = true;
if (oldTempl == newTempl)
return false;
fo.setAttribute(DataObject.PROP_TEMPLATE, (newTempl ? new Boolean(true) : null));
return true;
}
/** Set the template status of this data object.
* @param newTempl <code>true</code> if the object should be a template
* @exception IOException if setting the template state fails
*/
public final void setTemplate (boolean newTempl) throws IOException {
if (!setTemplate (getPrimaryFile(), newTempl)) {
// no change in state
return;
}
firePropertyChange(DataObject.PROP_TEMPLATE, new Boolean(!newTempl), new Boolean(newTempl));
}
/** Get the template status of this data object.
* @return <code>true</code> if it is a template
*/
public final boolean isTemplate () {
Object o = getPrimaryFile().getAttribute(PROP_TEMPLATE);
boolean ret = false;
if (o instanceof Boolean)
ret = ((Boolean) o).booleanValue();
return ret;
}
/** Test whether the object may be deleted.
* @return <code>true</code> if it may
*/
public abstract boolean isDeleteAllowed ();
/** Test whether the object may be copied.
* @return <code>true</code> if it may
*/
public abstract boolean isCopyAllowed ();
/** Test whether the object may be moved.
* @return <code>true</code> if it may
*/
public abstract boolean isMoveAllowed ();
/** Test whether the object may create shadows.
* <p>The default implementation returns <code>true</code>.
* @return <code>true</code> if it may
*/
public boolean isShadowAllowed () {
return true;
}
/** Test whether the object may be renamed.
* @return <code>true</code> if it may
*/
public abstract boolean isRenameAllowed ();
/** Test whether the object is modified.
* @return <code>true</code> if it is modified
*/
public boolean isModified() {
return modif;
}
/** Set whether the object is considered modified.
* Also fires a change event.
* If the new value is <code>true</code>, the data object is added into a {@link #getRegistry registry} of opened data objects.
* If the new value is <code>false</code>,
* the data object is removed from the registry.
*/
public void setModified(boolean modif) {
if (this.modif != modif) {
this.modif = modif;
if (modif) {
syncModified.add (this);
} else {
syncModified.remove (this);
}
firePropertyChange(DataObject.PROP_MODIFIED, new Boolean(!modif), new Boolean(modif));
}
}
/** Get help context for this object.
* @return the help context
*/
public abstract HelpCtx getHelpCtx ();
/** Get the primary file for this data object.
* For example,
* Java source uses <code>*.java</code> and <code>*.class</code> files but the primary one is
* always <code>*.java</code>. Please note that two data objects are {@link #equals equivalent} if
* they use the same primary file.
* <p><em>Warning:</em> do not call {@link Node#getHandle} or {@link DefaultHandle#createHandle} in this method.
*
* @return the primary file
*/
public final FileObject getPrimaryFile () {
return item.primaryFile;
}
/** Finds the data object for a specified file object.
* @param fo file object
* @return the data object for that file
* @exception DataObjectNotFoundException if the file does not have a
* data object
*/
public static DataObject find (FileObject fo)
throws DataObjectNotFoundException {
try {
// try to scan directly the pool (holds only primary files)
DataObject obj = DataObjectPool.POOL.find (fo);
if (obj != null) {
return obj;
}
// try to use the loaders machinery
obj = TopManager.getDefault ().getLoaderPool ().findDataObject (fo);
if (obj != null) {
return obj;
}
} catch (DataObjectExistsException ex) {
return ex.getDataObject ();
} catch (IOException ex) {
}
throw new DataObjectNotFoundException (fo);
}
/** Get the registry containing all modified objects.
*
* @return the registry
*/
public static Registry getRegistry () {
return Registry.INSTANCE;
}
/** Get the name of the data object.
* <p>The default implementation uses the name of the primary file.
* @return the name
*/
public String getName () {
return getPrimaryFile ().getName ();
}
/** Get the folder this data object is stored in.
* @return the folder; <CODE>null</CODE> if the primary file
* is the {@link FileObject#isRoot root} of its filesystem
*/
public final DataFolder getFolder () {
FileObject fo = getPrimaryFile ().getParent ();
// could throw IllegalArgumentException but only if fo is not folder
// => then there is a bug in filesystem implementation
return fo == null ? null : DataFolder.findFolder (fo);
}
/** Copy this object to a folder. The copy of the object is required to
* be deletable and movable.
* <p>An event is fired, and atomicity is implemented.
* @param f the folder to copy the object to
* @exception IOException if something went wrong
* @return the new object
*/
public final DataObject copy (final DataFolder f) throws IOException {
final DataObject[] result = new DataObject[1];
FileSystem fs = f.getPrimaryFile ().getFileSystem ();
fs.runAtomicAction (new FileSystem.AtomicAction () {
public void run () throws IOException {
result[0] = handleCopy (f);
}
});
fireOperationEvent (
new OperationEvent.Copy (result[0], this), OperationEvent.COPY
);
return result[0];
}
/** Copy this object to a folder (implemented by subclasses).
* @param f target folder
* @return the new data object
* @exception IOException if an error occures
*/
protected abstract DataObject handleCopy (DataFolder f) throws IOException;
/** Delete this object.
* <p>Events are fired and atomicity is implemented.
* @exception IOException if an error occures
*/
public final synchronized void delete () throws IOException {
// the object is ready to be closed
FileSystem fs = getPrimaryFile ().getFileSystem ();
fs.runAtomicAction (new FileSystem.AtomicAction () {
public void run () throws IOException {
handleDelete ();
item.deregister(false);
item.setDataObject(null);
}
});
firePropertyChange (PROP_VALID, Boolean.FALSE, Boolean.TRUE);
fireOperationEvent (
new OperationEvent (this), OperationEvent.DELETE
);
}
/** Delete this object (implemented by subclasses).
* @exception IOException if an error occures
*/
protected abstract void handleDelete () throws IOException;
/** Rename this object.
* <p>Events are fired and atomicity is implemented.
*
* @param name the new name
*
* @exception IOException if an error occures
*/
public final synchronized void rename (final String name) throws IOException {
String oldName = getName ();
if (oldName.equals (name)) return; // the new name is the same as the old one
FileObject old = getPrimaryFile ();
// executes atomic action with renaming
FileSystem fs = old.getFileSystem ();
fs.runAtomicAction (new FileSystem.AtomicAction () {
public void run () throws IOException {
FileObject mf = handleRename (name);
item.changePrimaryFile (mf);
}
});
firePropertyChange (PROP_PRIMARY_FILE, old, getPrimaryFile ());
fireOperationEvent (
new OperationEvent.Rename (this, oldName), OperationEvent.RENAME
);
// rename succeed
firePropertyChange(DataObject.PROP_NAME, oldName, getName ());
}
/** Rename this object (implemented in subclasses).
*
* @param name name to rename the object to
* @return new primary file of the object
* @exception IOException if an error occures
*/
protected abstract FileObject handleRename (String name) throws IOException;
/** Move this object to another folder.
* <p>An event is fired and atomicity is implemented.
* @param df folder to move object to
* @exception IOException if an error occurs
*/
public final void move (final DataFolder df) throws IOException {
if ((getFolder () == null)) return; // cannot move filesystem root
if (df.equals (getFolder ())) return; // if the destination folder is the same as the current one ==>> do nothing
// executes atomic action for moving
FileObject old = getPrimaryFile ();
FileSystem fs = old.getFileSystem ();
fs.runAtomicAction (new FileSystem.AtomicAction () {
public void run () throws IOException {
FileObject mf = handleMove (df);
item.changePrimaryFile (mf);
}
});
firePropertyChange (PROP_PRIMARY_FILE, old, getPrimaryFile ());
fireOperationEvent (
new OperationEvent.Move (this, old), OperationEvent.MOVE
);
}
/** Move this object to another folder (implemented in subclasses).
*
* @param df target data folder
* @return new primary file of the object
* @exception IOException if an error occures
*/
protected abstract FileObject handleMove (DataFolder df) throws IOException;
/** Creates shadow for this object in specified folder (overridable in subclasses).
* <p>The default
* implementation creates a reference data shadow and pastes it into
* the specified folder.
*
* @param f the folder to create a shortcut in
* @return the shadow
*/
protected DataShadow handleCreateShadow (DataFolder f) throws IOException {
return DataShadow.create (f, this);
}
/** Creates shadow for this object in specified folder.
* <p>An event is fired and atomicity is implemented.
*
* @param f the folder to create shortcut in
* @return the shadow
*/
public final DataShadow createShadow (final DataFolder f) throws IOException {
final DataShadow[] result = new DataShadow[1];
FileSystem fs = f.getPrimaryFile ().getFileSystem ();
fs.runAtomicAction (new FileSystem.AtomicAction () {
public void run () throws IOException {
result[0] = handleCreateShadow (f);
}
});
fireOperationEvent (
new OperationEvent.Copy (result[0], this), OperationEvent.SHADOW
);
return result[0];
}
/** Create a new object from template (with a name depending on the template).
*
* @param f folder to create object in
* @return new data object based on this one
* @exception IOException if an error occured
* @see #createFromTemplate(DataFolder,String)
*/
public final DataObject createFromTemplate (DataFolder f)
throws IOException {
return createFromTemplate (f, null);
}
/** Create a new object from template.
* Asks {@link #handleCreateFromTemplate}.
*
* @param f folder to create object in
* @param name name of object that should be created, or <CODE>null</CODE> if the
* name should be same as that of the template (or otherwise mechanically generated)
* @return the new data object
* @exception IOException if an error occured
*/
public final DataObject createFromTemplate (
final DataFolder f, final String name
) throws IOException {
final DataObject[] result = new DataObject[1];
FileSystem fs = f.getPrimaryFile ().getFileSystem ();
fs.runAtomicAction (new FileSystem.AtomicAction () {
public void run () throws IOException {
result[0] = handleCreateFromTemplate (f, name);
}
});
fireOperationEvent (
new OperationEvent.Copy (result[0], this), OperationEvent.TEMPL
);
return result[0];
}
/** Create a new data object from template (implemented in subclasses).
* This method should
* copy the content of the template to the destination folder and assign a new name
* to the new object.
*
* @param df data folder to create object in
* @param name name to give to the new object (or <CODE>null</CODE>
* if the name should be chosen according to the template)
* @return the new data object
* @exception IOException if an error occured
*/
protected abstract DataObject handleCreateFromTemplate (
DataFolder df, String name
) throws IOException;
/** Fires operation event to data loader pool.
* @param ev the event
* @param type OperationEvent.XXXX constant
*/
private static void fireOperationEvent (OperationEvent ev, int type) {
TopManager.getDefault ().getLoaderPool ().fireOperationEvent (ev, type);
}
//
// Property change support
//
/** Getter for event listener list.
*/
private EventListenerList listeners () {
if (listeners == null) {
synchronized (this) {
if (listeners == null) {
listeners = new EventListenerList ();
}
}
}
return listeners;
}
/** @param l the listener
*/
public void addPropertyChangeListener (PropertyChangeListener l) {
listeners ().add (PropertyChangeListener.class, l);
}
/** @param l the listener
*/
public void removePropertyChangeListener (PropertyChangeListener l) {
listeners ().remove (PropertyChangeListener.class, l);
}
/** Fires property change notification to all listeners registered via
* {@link #addPropertyChangeListener}.
*
* @param name of property
* @param oldValue old value
* @param newValue new value
*/
protected final void firePropertyChange (String name, Object oldValue, Object newValue) {
// Guaranteed to return a non-null array
Object[] listeners = listeners ().getListenerList();
PropertyChangeEvent fooEvent = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == PropertyChangeListener.class) {
// Lazily create the event:
if (fooEvent == null) {
fooEvent = new PropertyChangeEvent (this, name, oldValue, newValue);
}
((PropertyChangeListener)listeners[i+1]).propertyChange (fooEvent);
}
}
}
//
// Property change support
//
/** @param l the listener
*/
public void addVetoableChangeListener (VetoableChangeListener l) {
listeners ().add (VetoableChangeListener.class, l);
}
/** @param l the listener
*/
public void removeVetoableChangeListener (VetoableChangeListener l) {
listeners ().remove (VetoableChangeListener.class, l);
}
/** Fires vetoable change notification.
*
* @param name of property
* @param oldValue old value
* @param newValue new value
* @exception PropertyVetoException if the change has been vetoed
*/
protected final void fireVetoableChange (
String name, Object oldValue, Object newValue
) throws PropertyVetoException {
// Guaranteed to return a non-null array
Object[] listeners = listeners ().getListenerList();
PropertyChangeEvent fooEvent = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == VetoableChangeListener.class) {
// Lazily create the event:
if (fooEvent == null) {
fooEvent = new PropertyChangeEvent (this, name, oldValue, newValue);
}
((VetoableChangeListener)listeners[i+1]).vetoableChange (fooEvent);
}
}
}
//
// Cookie
//
/** Obtain a cookie from the data object.
* May be overridden by subclasses to extend the behaviour of
* data objects.
* <P>
* The default implementation tests if this object is of the requested class and
* if so, returns it.
*
* @param c class of requested cookie
* @return a cookie or <code>null</code> if such cookies are not supported
*/
public Node.Cookie getCookie (Class c) {
if (c.isInstance (this)) {
return this;
}
return null;
}
// =======================
// Serialization methods
//
/** The Serialization replacement for this object stores the primary file instead.
* @return a replacement
*/
public Object writeReplace () {
return new Replace (this);
}
/** The default replace for the data object
*/
private static final class Replace extends Object implements Serializable {
/** the primary file */
private FileObject fo;
/** the object to return */
private transient DataObject obj;
static final long serialVersionUID =-627843044348243058L;
/** Constructor.
* @param obj the object to use
*/
public Replace (DataObject obj) {
this.obj = obj;
this.fo = obj.getPrimaryFile ();
}
public Object readResolve () {
return obj;
}
/** Read method */
private void readObject (ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject ();
// can also throw an IOException
obj = find (fo);
}
}
/** Getter for a text from resource bundle.
*/
static String getString (String name) {
return NbBundle.getBundle ("org.openide.loaders.Bundle").getString (name);
}
/** Registry of modified data objects.
* The registry permits attaching of a change listener
* to be informed when the count of modified objects changes.
*/
public static final class Registry extends Object {
/** the only instance */
private static Registry INSTANCE = new Registry ();
/** Private constructor */
private Registry () {
}
/** Add new listener to changes in the set of modified objects.
* @param chl listener to add
*/
public void addChangeListener (final ChangeListener chl) {
modified.addChangeListener(chl);
}
/** Remove a listener to changes in the set of modified objects.
* @param chl listener to remove
*/
public void removeChangeListener (final ChangeListener chl) {
modified.removeChangeListener(chl);
}
/** Get a set of modified data objects.
* @return an unmodifiable set of {@link DataObject}s
*/
public Set getModifiedSet () {
return Collections.unmodifiableSet(syncModified);
}
/** Get modified objects.
* @return array of objects
*/
public DataObject[] getModified () {
return (DataObject[])modified.toArray (new DataObject[0]);
}
}
private static final class ModifiedRegistry extends HashSet {
/** Set of listeners listening to changes to the set of modified objs
* @associates ChangeListener*/
private HashSet listeners;
static final long serialVersionUID =-2861723614638919680L;
/** Adds new listener.
* @param chl new listener
*/
public final synchronized void addChangeListener (final ChangeListener chl) {
if (listeners == null) listeners = new HashSet(5);
listeners.add(chl);
}
/** Removes listener from the listener list.
* @param chl listener to remove
*/
public final synchronized void removeChangeListener (final ChangeListener chl) {
if (listeners == null) return;
listeners.remove(chl);
}
/***** overriding of methods which change content in order to notify
* listeners about the content change */
public boolean add (Object o) {
boolean result = super.add(o);
if (result) fireChangeEvent(new ChangeEvent(this));
return result;
}
public boolean remove (Object o) {
boolean result = super.remove(o);
if (result) fireChangeEvent(new ChangeEvent(this));
return result;
}
/** Fires change event to all listeners.
* @param che change event
*/
protected final void fireChangeEvent (ChangeEvent che) {
if (listeners == null) return;
HashSet cloned;
// clone listener list
synchronized (this) {
cloned = (HashSet)listeners.clone();
}
// fire on cloned list to prevent from modifications when firing
for (Iterator iter = cloned.iterator(); iter.hasNext(); ) {
((ChangeListener)iter.next()).stateChanged(che);
}
}
} // end of ModifiedRegistry inner class
}
/*
* Log
* 27 Gandalf 1.18.1.7 11/12/98 Jaroslav Tulach
* 26 Gandalf 1.18.1.6 11/11/98 Ian Formanek
* 25 Gandalf 1.18.1.5 11/09/98 Ian Formanek
* 24 Gandalf 1.18.1.4 11/05/98 Jaroslav Tulach Special properties for
* Folder.
* 23 Gandalf 1.18.1.3 11/05/98 Jaroslav Tulach Sorting and ordering of
* DataFolders.
*
* 22 Gandalf 1.18.1.2 11/04/98 Jaroslav Tulach Recognization of folders.
* 21 Gandalf 1.18.1.1 10/30/98 Jaroslav Tulach
* 20 Gandalf 1.18.1.0 10/30/98 Jaroslav Tulach
* 19 Tuborg 1.18 10/13/98 Jaroslav Tulach Can switch template when
* text in Java editor is
* modified.
* 18 Tuborg 1.17 09/03/98 Ian Formanek Fixed bug 596 - If an
* object is cutted and pasted
* into the same folder, the
* explorer displays two nodes
* for this object and it does
* not behave correctly.
* 17 Tuborg 1.16 09/03/98 Ian Formanek The Rename action on
* DataObject chacks whether
* the new name is not the
* same as the old one
* 16 Tuborg 1.15 08/21/98 Jaroslav Tulach serialVersionUID + compiles
* 15 Tuborg 1.14 08/21/98 Jaroslav Tulach serialVersionUID
* 14 Tuborg 1.13 08/07/98 Ian Formanek Reflecting Save action
* changes (it is now a
* CookieAction) - the
* SaveAction must be
* refreshed after setting
* modified flag
* 13 Tuborg 1.12 07/31/98 Jan Jancura Customize as in Customize
* bean action
* 12 Tuborg 1.11 07/22/98 Jan Palka Remove check
* 11 Tuborg 1.10 07/17/98 Jan Palka Add check if a given
* template name is valid
* 10 Tuborg 1.9 07/15/98 Ales Novak
* 9 Tuborg 1.8 07/10/98 Ales Novak
* 8 Tuborg 1.7 07/10/98 Jaroslav Tulach
* 7 Tuborg 1.6 07/10/98 Jaroslav Tulach
* 6 Tuborg 1.5 07/10/98 Jaroslav Tulach
* 5 Tuborg 1.4 07/10/98 Ales Novak
* 4 Tuborg 1.3 07/03/98 Ian Formanek Setting isTemplate property
* is back
* 3 Tuborg 1.2 06/16/98 Jaroslav Tulach
* 2 Tuborg 1.1 06/15/98 Ian Formanek
* 1 Tuborg 1.0 06/11/98 David Peroutka
* $
* Beta Change History:
* 0 Tuborg 1.00 --/--/98 Jaroslav Tulach new design, composed DataHandler together with DataObject
* 0 Tuborg 1.02 --/--/98 Jaroslav Tulach removed line set, created cookie LineCookie
* 0 Tuborg 1.03 --/--/98 Jaroslav Tulach find does not return null
* 0 Tuborg 1.04 --/--/98 Jaroslav Tulach sometimes returns FilterNode, sometimes SerializableFilterNode
* 0 Tuborg 1.05 --/--/98 Jaroslav Tulach defaultInstantiateTemplate method
* 0 Tuborg 1.06 --/--/98 Jan Formanek bugfix
* 0 Tuborg 1.07 --/--/98 Jan Formanek isModified () added
* 0 Tuborg 1.08 --/--/98 Petr Hamernik isModified changed
* 0 Tuborg 1.09 --/--/98 Petr Hamernik opened objects and opened views removed, modified objects added
* 0 Tuborg 1.10 --/--/98 Jan Formanek "File" PropertySet removed (commented out)
* 0 Tuborg 1.11 --/--/98 Petr Hamernik rename cookie
* 0 Tuborg 1.12 --/--/98 Jan Formanek reflecting changes in cookies
* 0 Tuborg 1.14 --/--/98 Jan Formanek removed full.hack
* 0 Tuborg 1.15 --/--/98 Jan Formanek improved focus in createFromTemplate input line
*/